<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div id="app">
      <p>{{title}}</p>
      <p></p>
      <p></p>
    </div>
    <script>
      function h(tag, props, children) {
        return { tag, props, children };
      }
      // 1.基本结构
      const Vue = {
        // 扩展性
        createRenderer({ querySelector, insert, createElement, remove }) {
          // 返回渲染器
          return {
            createApp(options) {
              return {
                mount(selector) {
                  // 1. 找到宿主元素
                  const parent = querySelector(selector);
                  // 2. 渲染页面
                  if (!options.render) {
                    //  2.1 处理template：编译
                    options.render = this.compile(parent.innerHTML);
                  }
                  // setup和其他选项
                  const proxy = options.setup();
                  //  2.2 用户直接编写render
                  this.update = effect(() => {
                    // const el = options.render.call(proxy);
                    // parent.innerHTML = "";
                    // // 3. 追加到宿主
                    // insert(el, parent);

                    const vnode = options.render.call(proxy);
                    // 转换vnode为dom
                    // 初始化创建整棵树
                    if (!this.isMouted) {
                      // 实现createElm，整体创建
                      const el = this.createElm(vnode);
                      parent.innerHTML = "";
                      insert(el, parent);
                      // init
                      this.isMouted = true;
                    } else {
                      // todo: update
                      // 新老节点对比
                      this.patch(this._vnode, vnode);
                    }
                    this._vnode = vnode;
                  });
                  this.update();
                },
                patch(n1, n2) {
                  const el = (n2.el = n1.el);
                  // n1是老节点，n2是新节点
                  // 1.更新：必须更新相同节点
                  // 什么样的是相同节点
                  // tag，key
                  // sameVnode
                  if (n1.tag === n2.tag && n1.key === n2.key) {
                    // update
                    // props todo
                    // children
                    const oldCh = n1.children;
                    const newCh = n2.children;
                    if (typeof oldCh === "string") {
                      if (typeof newCh === "string") {
                        // text update
                        if (oldCh !== newCh) {
                          el.textContent = newCh;
                        }
                      } else {
                        // 替换文本
                        // 清空再创建并追加
                        el.textContent = "";
                        newCh.forEach((child) =>
                          insert(this.createElm(child), el)
                        );
                      }
                    } else {
                      if (typeof newCh === "string") {
                        // 替换一组子元素为文本，textContent可以连带删除清空并设置文本
                        el.textContent = newCh;
                      } else {
                        // updateChildren
                        this.updateChildren(el, oldCh, newCh);
                      }
                    }
                  } else {
                    // replace
                  }
                },
                updateChildren(el, oldCh, newCh) {
                  // 1.获取newCh和oldCh中较短的那一个
                  const len = Math.min(oldCh.length, newCh.length);
                  for (let i = 0; i < len; i++) {
                    this.patch(oldCh[i], newCh[i]);
                  }
                  // 处理剩余元素
                  // 新数组元素多
                  if (newCh.length > oldCh.length) {
                    // 批量创建并追加
                    // 截取newCh中len后面的部分
                    newCh.slice(len).forEach((child) => {
                      insert(this.createElm(child), el);
                    });
                  } else if (newCh.length < oldCh.length) {
                    oldCh.slice(len).forEach((child) => {
                      remove(child.el, el);
                    });
                  }
                },
                createElm(vnode) {
                  const { tag, props, children } = vnode;
                  // 遍历vnode，创建整棵树
                  const el = createElement(tag);
                  // 如果存在属性，就设置它们
                  // el.setAttribute(key, val)
                  // 递归
                  // 判断children是否字符串
                  if (typeof children == "string") {
                    el.textContent = children;
                  } else {
                    children.forEach((child) =>
                      insert(this.createElm(child), el)
                    );
                  }
                  // vnode中要保存真实dom，以备未来更新使用
                  vnode.el = el;
                  return el;
                },
                compile(template) {
                  // 返回一个render函数
                  // parse -> ast
                  // generate -> ast => render
                  return function render() {
                    // const h3 = document.createElement("h3");
                    // h3.textContent = this.title;
                    // return h3;
                    if (Array.isArray(this.title)) {
                      return h(
                        "h3",
                        null,
                        this.title.map((s) => h("p", null, s))
                      );
                    } else {
                      return h("h3", null, this.title);
                    }
                    // return h("h3", null, [
                    //   h("p", null, this.title),
                    //   h("p", null, this.title),
                    //   h("p", null, this.title),
                    // ]);
                  };
                },
              };
            },
          };
        },
        createApp(options) {
          // 创建一个web平台特有的渲染器
          const renderer = Vue.createRenderer({
            querySelector(sel) {
              return document.querySelector(sel);
            },
            insert(el, parent) {
              parent.appendChild(el);
            },
            createElement(tag) {
              return document.createElement(tag);
            },
            remove(el, parent) {
              parent.removeChild(el);
            },
          });
          return renderer.createApp(options);
        },
      };
    </script>
    <script>
      // 能够拦截用户对代理对象的访问
      // 从而在值发生变化的时候做出响应式
      function reactive(obj) {
        // vue2 Object.defineProperty
        return new Proxy(obj, {
          get(target, key) {
            console.log("get key", key);
            // 建立依赖关系
            track(target, key);
            return target[key];
          },
          set(target, key, val) {
            console.log("set key", key);
            target[key] = val;
            // 触发更新
            trigger(target, key);
            // 通知更新
            // app.update();
          },
        });
      }

      // 建立映射关系：依赖dep - 组件更新函数
      // vue2： 1组件有watcher
      // vue3： 创建map结构 { target: { key: [update1, update2] }}

      // 调用effect，首先执行fn
      const effectStack = [];
      function effect(fn) {
        const eff = function () {
          try {
            effectStack.push(eff);
            fn();
          } finally {
            effectStack.pop();
          }
        };
        // 立即调用一次
        eff();
        return eff;
      }

      const targetMap = {};
      // 建立target，key和effectStack中存储的副作用函数之间的关系
      function track(target, key) {
        const effect = effectStack[effectStack.length - 1];
        // 判断target为key的对象是否存在
        let map = targetMap[target];
        if (!map) {
          map = targetMap[target] = {};
        }
        let deps = map[key];
        if (!deps) {
          deps = map[key] = [];
        }
        // 映射关系建立
        if (deps.indexOf(effect)) {
          deps.push(effect);
        }
      }

      function trigger(target, key) {
        const map = targetMap[target];
        if (map) {
          const deps = map[key];
          if (deps) {
            deps.forEach((dep) => dep());
          }
        }
      }

      const app = Vue.createApp({
        setup() {
          const state = reactive({
            title: "vue3, hello!".split(""),
          });
          setTimeout(() => {
            state.title = "hello, vue3!".split("");
          }, 2000);
          return state;
        },
      });
      app.mount("#app");
    </script>
  </body>
</html>
